using System;
using System.Reflection;
using System.Runtime.CompilerServices;
using System.Text;

namespace DocGenerator
{
	public static class MethodInfoExtensions
	{
		/// <summary>
		/// Return the method signature as a string.
		/// </summary>
		/// <param name="method">The Method</param>
		/// <param name="callable">Return as an callable string(public void a(string b) would return a(b))</param>
		/// <returns>Method signature</returns>
		public static string GetSignature(this MethodInfo method, bool callable = false)
		{
			var firstParam = true;
			var sigBuilder = new StringBuilder();
			sigBuilder.Append(method.Name);

			// Add method generics
			if (method.IsGenericMethod)
			{
				sigBuilder.Append("<");
				foreach (var g in method.GetGenericArguments())
				{
					if (firstParam)
						firstParam = false;
					else
						sigBuilder.Append(", ");
					sigBuilder.Append(TypeName(g));
				}
				sigBuilder.Append(">");
			}
			sigBuilder.Append("(");
			firstParam = true;
			var secondParam = false;
			foreach (var param in method.GetParameters())
			{
				if (firstParam)
				{
					firstParam = false;
					if (method.IsDefined(typeof(ExtensionAttribute), false))
					{
						if (callable)
						{
							secondParam = true;
							continue;
						}
						sigBuilder.Append("this ");
					}
				}
				else if (secondParam == true)
					secondParam = false;
				else
					sigBuilder.Append(", ");
				if (param.ParameterType.IsByRef)
					sigBuilder.Append("ref ");
				else if (param.IsOut)
					sigBuilder.Append("out ");
				if (!callable)
				{
					sigBuilder.Append(TypeName(param.ParameterType));
					sigBuilder.Append(' ');
				}
				sigBuilder.Append(param.Name);

				if (param.HasDefaultValue)
				{
					object defaultValue;
					if (param.ParameterType == typeof(bool))
						defaultValue = (bool)param.DefaultValue ? "true" : "false";
					else
						defaultValue = param.DefaultValue;

					sigBuilder.Append(" = " + defaultValue);
				}
			}
			sigBuilder.Append(")");
			return sigBuilder.ToString();
		}

		/// <summary>
		/// Get full type name with full namespace names
		/// </summary>
		/// <param name="type">Type. May be generic or nullable</param>
		/// <returns>Full type name, fully qualified namespaces</returns>
		public static string TypeName(Type type)
		{
			var nullableType = Nullable.GetUnderlyingType(type);
			if (nullableType != null)
				return nullableType.Name + "?";

			if (!type.IsGenericType)
			{
				switch (type.Name)
				{
					case "String": return "string";
					case "Int32": return "int";
					case "Decimal": return "decimal";
					case "Object": return "object";
					case "Void": return "void";
					default:
					{
						return string.IsNullOrWhiteSpace(type.FullName) ? type.Name : type.FullName;
					}
				}
			}

			var sb = new StringBuilder(type.Name.Substring(0,
				type.Name.IndexOf('`'))
			);
			sb.Append('<');
			var first = true;
			foreach (var t in type.GetGenericArguments())
			{
				if (!first)
					sb.Append(',');
				sb.Append(TypeName(t));
				first = false;
			}
			sb.Append('>');
			return sb.ToString();
		}
	}
}
